#include <strings.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <google/protobuf/message.h>
#include <fstream>
#include <pcap.h>
#include <boost/filesystem.hpp>

#include "multi.pb.h"
#include "hmac.h"
#include "server.h"
#include <openssl/hmac.h>
#include <netinet/ip.h>
#include <ctime>
 #include <signal.h>

#define DOWNLOAD_FOLDER "/diode/download/"
#define UPLOAD_FOLDER "/diode/upload/"

#define MULTICASTIP "127.0.0.1"
#define CLIENT_MULTICASTPORT 3200
#define BLOB_SIZE 1000

#define ETHER_TYPE_IP (0x0800)
#define ETHER_TYPE_8021Q (0x8100)

namespace receiver
{

bool g_running_receiver = true;

class Heartbeat_t {
public:
	Heartbeat_t() {
		pthread_mutex_lock(&m_lock);
		m_time = time(0);
		pthread_mutex_unlock(&m_lock);
	}
	void tick() {
		pthread_mutex_lock(&m_lock);
		m_time = time(0);
		pthread_mutex_unlock(&m_lock);
	}
	int check() {
		pthread_mutex_lock(&m_lock);
		time_t tmp = time(0) - m_time;
		pthread_mutex_unlock(&m_lock);
		return tmp;
	}
private:
	time_t m_time;
	pthread_mutex_t m_lock;
};

Heartbeat_t HeartbeatValidator;

class udp_server {
public:
	udp_server(int port) {

		m_sockfd=socket(AF_INET,SOCK_DGRAM,0);

		bzero(&m_servaddr,sizeof(m_servaddr));
		m_servaddr.sin_family = AF_INET;
		m_servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
		m_servaddr.sin_port=htons(port);
		bind(m_sockfd,(struct sockaddr *)&m_servaddr,sizeof(m_servaddr));

		m_imreq.imr_multiaddr.s_addr = inet_addr(MULTICASTIP);
		m_imreq.imr_interface.s_addr = INADDR_ANY; // use DEFAULT interface

		setsockopt(m_sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
		           (const void *)&m_imreq, sizeof(struct ip_mreq));

	}
	server::Block getBlock() {
		while(1) {
			socklen_t len;
			char mesg[10000];
			len = sizeof(m_cliaddr);
			int byte_count = recvfrom(m_sockfd,mesg,10000,0,(struct sockaddr *)&m_cliaddr,&len);
			std::string data(mesg,byte_count);

			if(data.size() >20) {
				std::string hmacStr = data.substr(data.size() - 20,20);
				std::string blockData = data.substr(0,data.size() - 20);

				if(hmacStr == hmac(blockData)) {
					server::Block block;
					block.ParseFromString(blockData);
					return block;
				}
			}
		}
	}
	void rxBlock(server::Block const& block) {
		static int count = 0;

		static int lastSeq=0;
		static int fileID=0;
		static int fileSize=0;

		static std::ofstream myfile;

		if(block.has_data()) {
			if(fileID != block.data().fileid()) {
				std::cout << "########################### New fileID ##########" << std::endl;
				fileID = block.data().fileid();
				count = 0;
				fileSize=0;
				myfile.close();
				lastSeq = 0;
				myfile.open (std::string( DOWNLOAD_FOLDER + block.data().filename()).c_str());
			}


			if(lastSeq != block.data().seq())
				std::cout << "########################### Missing packet ##########" << std::endl;

			if(block.data().has_blob())
				fileSize += block.data().blob().size();

			if(block.data().has_blob())
				myfile << block.data().blob();

			if(fileSize == block.data().totalsize()) {
				myfile.close();
				std::cout << "total file downloaded" << std::endl;
			}

			lastSeq = 1 + block.data().seq();
		}
		if(block.has_heartbeat()) {
			std::cout << "Heartbeat" << std::endl;
			HeartbeatValidator.tick();
		}
		if(block.has_ctrl() && block.ctrl().has_updateserver()) {
			std::cout << "update server" << std::endl;
			if(rename("./download/server", "server"))
				std::cout << "File successfully renamed" << std::endl;
			else
				std::cout << "Error renaming file" << std::endl;
		}

		if(block.has_ctrl() && block.ctrl().has_runprogram()) {
			std::cout << "Run program" << std::endl;
			std::string command1 = "chmod 777 " + std::string(DOWNLOAD_FOLDER) + block.ctrl().filename();
			system(command1.c_str());

			std::string command2 = std::string(DOWNLOAD_FOLDER) + block.ctrl().filename() + " &";
			system(command2.c_str());
		}

		if(block.has_ctrl() && block.ctrl().has_killprogram()) {
			std::cout << "Kill program" << std::endl;
			std::string command1 = "killall -9 " + block.ctrl().filename();
			system(command1.c_str());
		}
	}
private:
	int m_sockfd,m_n;
	struct ip_mreq m_imreq;
	struct sockaddr_in m_servaddr,m_cliaddr;
};


void *server_system_receiver( void *ptr ) {


}



void server_system() {


	udp_server client_process(CLIENT_MULTICASTPORT);


	if(boost::filesystem::create_directories(DOWNLOAD_FOLDER)) {
		std::cout << "Download folder Success created" << "\n";
	}

	while(g_running_receiver) {
		server::Block block(client_process.getBlock());

		client_process.rxBlock(block);
	}

}


void readPcap(std::string const& pcapFileName) {
	unsigned int pkt_counter=0;   // packet counter
	unsigned long byte_counter=0; //total bytes seen in entire trace
	unsigned long cur_counter=0; //counter for current 1-second interval
	unsigned long max_volume = 0;  //max value of bytes in one-second interval
	unsigned long current_ts=0; //current timestamp

	//temporary packet buffers
	struct pcap_pkthdr header; // The header that pcap gives us
	const u_char *packet; // The actual packet


	//-------- Begin Main Packet Processing Loop -------------------
	//loop through each pcap file in command line args
	pcap_t *handle;
	char errbuf[PCAP_ERRBUF_SIZE]; //not sure what to do with this, oh well
	handle = pcap_open_offline(pcapFileName.c_str(), errbuf);   //call pcap library function

	if (handle == NULL) {
		std::cout << "Couldn't open pcap file" << std::endl;
		return;
	}


	while (packet = pcap_next(handle,&header)) {
		// header contains information about the packet (e.g. timestamp)
		u_char *pkt_ptr = (u_char *)packet; //cast a pointer to the packet data

		//parse the first (ethernet) header, grabbing the type field
		int ether_type = ((int)(pkt_ptr[12]) << 8) | (int)pkt_ptr[13];
		int ether_offset = 0;

		if (ether_type == ETHER_TYPE_IP) { //most common
			std::cout << "ip packet" << std::endl;
			ether_offset = 14;
		} else if (ether_type == ETHER_TYPE_8021Q) //my traces have this
			ether_offset = 18;

		//parse the IP header
		pkt_ptr += ether_offset;  //skip past the Ethernet II header
		struct ip *ip_hdr = (struct ip *)pkt_ptr; //point to an IP header structure

		int packet_length = ntohs(ip_hdr->ip_len);

		//check to see if the next second has started, for statistics purposes
		if (current_ts == 0) {  //this takes care of the very first packet seen
			current_ts = header.ts.tv_sec;
		} else if (header.ts.tv_sec > current_ts) {
			printf("%lu KBps\n", cur_counter/1000); //print
			cur_counter = 0; //reset counters
			current_ts = header.ts.tv_sec; //update time interval
		}

		cur_counter += packet_length;
		byte_counter += packet_length; //byte counter update
		pkt_counter++; //increment number of packets seen

	} //end internal loop for reading packets (all in one file)

	pcap_close(handle);  //close the pcap file

	byte_counter /= 1e6;  //convert to MB to make easier to read

	printf("Processed %d packets and %lu MBytes\n", pkt_counter, byte_counter);
}



void* heartbeatReceiverThread(void *arg) {
	while(g_running_receiver) {
		if(HeartbeatValidator.check() > 3)
			std::cout << "Missing heartbeat" << std::endl;
		sleep(1);
	}
	return NULL;
}

void* serverSystemThread(void *arg) 
{
	server_system();
	return NULL;
}

Receiver::Receiver() {
	pthread_create(&(m_heartbeatProcess), NULL, &heartbeatReceiverThread, NULL);
	pthread_create(&(m_serverSystemProcess), NULL, &serverSystemThread, NULL);
}
void Receiver::join() {
	pthread_join(m_heartbeatProcess, NULL);
}


void Receiver::stop() {
	g_running_receiver = false;
//	pthread_kill(m_heartbeatProcess, SIGKILL);
}

}
/*
int main(int argc, char**argv) {
	srand (time(NULL));

	if(std::string(argv[0]) == "./server") {
		Receiver rx;
		rx.join();

	}

	if(argc < 2 ) {
		std::cout << "client or server? =\"" << argv[0] << "\""  << std::endl;
		return 1;
	}

	if(std::string(argv[1]) == "pcap")
		readPcap(std::string(argv[2]));

	return 0;
}*/
